{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "西瓜书习题4.3：试编程实现基于信息熵进行划分选择的决策树算法，并为表 4.3 中数据生成一棵决策树。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -*- coding: utf-8 -*\n",
    "import numpy as np\n",
    "import pandas as pd\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    Color       Root   Knocks   Texture Umbilicus Touch  Label\n",
      "0   green       curl  heavily  distinct    sunken  hard      1\n",
      "1   black       curl     dull  distinct    sunken  hard      1\n",
      "2   black       curl  heavily  distinct    sunken  hard      1\n",
      "3   green       curl     dull  distinct    sunken  hard      1\n",
      "4   white       curl  heavily  distinct    sunken  hard      1\n",
      "5   green  lightCurl  heavily  distinct    dimple  soft      1\n",
      "6   black  lightCurl  heavily      blur    dimple  soft      1\n",
      "7   black  lightCurl  heavily  distinct    dimple  hard      1\n",
      "8   black  lightCurl     dull      blur    dimple  hard      0\n",
      "9   green      stiff    clear  distinct    smooth  soft      0\n",
      "10  white      stiff    clear     fuzzy    smooth  hard      0\n",
      "11  white       curl  heavily     fuzzy    smooth  soft      0\n",
      "12  green  lightCurl  heavily      blur    sunken  hard      0\n",
      "13  white  lightCurl     dull      blur    sunken  hard      0\n",
      "14  black  lightCurl  heavily  distinct    dimple  soft      0\n",
      "15  white       curl  heavily     fuzzy    smooth  hard      0\n",
      "16  green       curl     dull      blur    dimple  hard      0\n"
     ]
    }
   ],
   "source": [
    "df = pd.read_csv('watermelon3_0_En.csv', encoding = \"utf-8\")\n",
    "df = df.drop(['No.','Density','SugerRatio'],axis=1)\n",
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "结点类：\n",
    "\n",
    "@para attribute：结点要划分的属性\n",
    "@para label：结点的类别\n",
    "@para attribute_next：下一个结点\n",
    "'''\n",
    "class Node(object):\n",
    "    def __init__(self, attribute=None, label=None, node_next={}):\n",
    "        self.attribute = attribute\n",
    "        self.label = label\n",
    "        self.node_next = node_next\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "统计数据集中样例各个类别的数量，在西瓜数据3.0中即为是和否的数量：\n",
    "\n",
    "@para   df：数据集\n",
    "@return label_count：各个类别的数量：label_count[0]:正例数量，label_count[1]反例数量\n",
    "'''\n",
    "def LabelCount(df):\n",
    "    label_count=[]\n",
    "    label_count.append(df[df['Label']==1]['Label'].count())\n",
    "    label_count.append(df[df['Label']==0]['Label'].count())\n",
    "    return label_count"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "样本数据比较\n",
    "\n",
    "@para    df：数据集\n",
    "@return  True：表示数据集中所有样本在所有属性上取值相同\n",
    "         False：数据集中又不同取值\n",
    "'''\n",
    "def DataCompare(df, A):\n",
    "    if len(df.shape)==0 :\n",
    "        return True\n",
    "    a = list(attr_set.keys())\n",
    "    for row in range(df.shape[0]-1): \n",
    "        com  = df[a].iloc[0] == df[a].iloc[row+1]#iloc:索引第i行；loc：索引行标签为i的行\n",
    "        if(com[com==True].count()<com.count()):\n",
    "            return False\n",
    "    return True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "创建属性集\n",
    "\n",
    "@para    df：数据集\n",
    "@return  attr_set:属性集\n",
    "                 字典格式如：{'敲声': ['浊响', '沉闷', '清脆']}\n",
    "'''\n",
    "def CreateAttrSet(df):\n",
    "    attr_set = {}\n",
    "    attr_name = df.columns\n",
    "    for i in attr_name[:6]:\n",
    "        attr_set[i] = df[i].unique()\n",
    "    return attr_set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "挑选数据集中A属性中包含a值的样例\n",
    "'''\n",
    "def DataSelect(df, A, a):\n",
    "    return df[df[A]==a]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "基于信息增益属性选择：ID3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "计算信息熵\n",
    "@para    df：数据集\n",
    "@return  ent_d:数据集df的信息熵\n",
    "'''\n",
    "def GetEnt(df):\n",
    "    ent_d = 0.0\n",
    "    classes = LabelCount(df)\n",
    "    class_sum = len(df)\n",
    "    for k in classes:        \n",
    "        if k!=0 :\n",
    "            p_k = k/class_sum\n",
    "            ent_d += - p_k*np.log2(p_k)\n",
    "    return ent_d\n",
    "'''\n",
    "计算信息增益\n",
    "\n",
    "@para    df：数据集\n",
    "@para    attr:需要计算信息增益的属性\n",
    "@return  gain:属性attr的信息增益\n",
    "'''\n",
    "def GetGain(df, attr):\n",
    "    ent_d = GetEnt(df)\n",
    "    gain_temp = 0.0\n",
    "    for v in attr[1]:\n",
    "        df_v = df[df[attr[0]]==v]#挑选数据集中attr[0]属性中包含v值的样例\n",
    "        gain_temp += len(df_v)/len(df)*GetEnt(df_v)\n",
    "    gain = GetEnt(df) - gain_temp\n",
    "    return gain\n",
    "'''\n",
    "基于信息增益属性选择\n",
    "\n",
    "@para    df：数据集\n",
    "@para    attr_set:属性集\n",
    "@return  best_attr:属性集中信息增益最大的属性\n",
    "''' \n",
    "def AttrSelectBaseGain(df, attr_set):\n",
    "    best_gain = 0.0\n",
    "    best_attr = None\n",
    "    for attr in attr_set.items():\n",
    "        attr_gain = GetGain(df, attr)\n",
    "        if attr_gain>best_gain :\n",
    "            best_gain = attr_gain\n",
    "            best_attr = attr\n",
    "    return best_attr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "基于增益率属性选择：C4.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "计算增益率中的属性固有值\n",
    "\n",
    "@para    df：数据集\n",
    "@para    att:需要计算增益率的属性\n",
    "@return  iv_attr:属性attr的增益率\n",
    "'''\n",
    "def IV(df, attr):\n",
    "    iv_attr = 0.0 #属性attr的固有值\n",
    "    for v in attr[1]:\n",
    "        df_v = df[df[attr[0]]==v]#挑选数据集中attr[0]属性中包含v值的样例\n",
    "        if len(df_v)!=0 :\n",
    "            iv_attr -= len(df_v)/len(df) * np.log2(len(df_v)/len(df))\n",
    "    return iv_attr\n",
    "'''\n",
    "基于增益率属性选择\n",
    "\n",
    "@para    df：数据集\n",
    "@para    attr_set:属性集\n",
    "@return  best_attr:属性集中增益率最大的属性\n",
    "'''\n",
    "def AttrSelectBaseGainRatio(df,attr_set):\n",
    "    best_gain_ratio = 0.0\n",
    "    best_attr = None\n",
    "    for attr in attr_set.items():\n",
    "        gain_ratio = GetGain(df, attr)/IV(df, attr)\n",
    "        if gain_ratio>best_gain_ratio :\n",
    "            best_gain_ratio = gain_ratio\n",
    "            best_attr = attr\n",
    "    return best_attr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "基于基尼指数属性选择：CART决策树"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "计算基尼值:西瓜书公式4.5\n",
    "\n",
    "@para    df：数据集\n",
    "@return  gini:数据集df的基尼值\n",
    "'''\n",
    "def GetGini(df):\n",
    "    label_count = LabelCount(df)\n",
    "    gini_temp = 0.0\n",
    "    for k in label_count:\n",
    "        if len(df)!=0:\n",
    "            gini_temp += np.square(k/len(df))\n",
    "    gini = 1-gini_temp\n",
    "    print('基尼值：',gini)\n",
    "    return gini\n",
    "'''\n",
    "计算属性attr的基尼指数：西瓜书公式4.6\n",
    "\n",
    "@para    df：数据集\n",
    "@para    attr:属性\n",
    "@return  gini_index:属性attr的基尼指数\n",
    "'''\n",
    "def GetGini_index(df,attr):\n",
    "    gini_index = 0.0\n",
    "    for v in attr[1]:\n",
    "        df_v = df[df[attr[0]]==v]\n",
    "        gini_index += len(df_v)/len(df)*GetGini(df_v)\n",
    "    print('基尼指数',gini_index)\n",
    "    return gini_index\n",
    "'''\n",
    "基于基尼指数属性选择\n",
    "\n",
    "@para    df：数据集\n",
    "@para    attr_set:属性集\n",
    "@return  best_attr:属性集中基尼指数最小的属性\n",
    "'''\n",
    "def AttrSelectBaseGainIndex(df,attr_set):\n",
    "    best_attr = None\n",
    "    best_gini_index = 100\n",
    "    for attr in attr_set.items():\n",
    "        gini_index = GetGini_index(df,attr)\n",
    "        \n",
    "        if best_gini_index>=gini_index:\n",
    "            best_gini_index = gini_index\n",
    "            best_attr = attr\n",
    "    print(best_gini_index)\n",
    "    return best_attr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "测试决策树绘图\n",
    "'''\n",
    "\n",
    "def DrawPNG(root, out_file):\n",
    "    '''\n",
    "    visualization of decision tree from root.\n",
    "    @param root: Node, the root node for tree.\n",
    "    @param out_file: str, name and path of output file\n",
    "    '''\n",
    "    try:\n",
    "        from pydotplus import graphviz\n",
    "    except ImportError:\n",
    "        print(\"module pydotplus.graphviz not found\")\n",
    "        \n",
    "    g = graphviz.Dot()  # generation of new dot   \n",
    "\n",
    "    TreeToGraph(0, g, root)\n",
    "    g2 = graphviz.graph_from_dot_data( g.to_string() )\n",
    "                                                                                            \n",
    "    g2.write_png(out_file) \n",
    "\n",
    "def TreeToGraph(i, g, root):\n",
    "    '''\n",
    "    build a graph from root on\n",
    "    @param i: node number in this tree\n",
    "    @param g: pydotplus.graphviz.Dot() object\n",
    "    @param root: the root node\n",
    "    \n",
    "    @return i: node number after modified  \n",
    "#   @return g: pydotplus.graphviz.Dot() object after modified\n",
    "    @return g_node: the current root node in graphviz\n",
    "    '''\n",
    "    try:\n",
    "        from pydotplus import graphviz\n",
    "    except ImportError:\n",
    "        print(\"module pydotplus.graphviz not found\")\n",
    "    \n",
    "    if root.attribute == None:\n",
    "        g_node_label = \"Node:%d\\nLabel:%s\" % (i, root.label)\n",
    "    else:\n",
    "        g_node_label = \"Node:%d\\nAttr:%s\" % (i, root.attribute)\n",
    "    g_node = i\n",
    "    g.add_node( graphviz.Node( g_node, label = g_node_label ))\n",
    "    \n",
    "    for value in list(root.node_next):\n",
    "        i, g_child = TreeToGraph(i+1, g, root.node_next[value])\n",
    "        g.add_edge( graphviz.Edge(g_node, g_child, label = value) ) \n",
    "\n",
    "    return i, g_node"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "创建决策树\n",
    "'''\n",
    "def CreateDecisionTree(data_set, attr_set):\n",
    "    node = Node(None, None, {})\n",
    "    label_count = LabelCount(data_set)\n",
    "    #情形1：数据集中包含的样本全属于同一类时，递归结束，返回\n",
    "    if(label_count[0]==data_set['Label'].count()):\n",
    "        node.label = 1\n",
    "        return node\n",
    "    if(label_count[1]==data_set['Label'].count()):\n",
    "        node.label = 0\n",
    "        return node\n",
    "    #情形2：属性集为空或样本在所有属性上取值相同，无法划分\n",
    "    if(len(attr_set)==0 or DataCompare(data_set, attr_set)==True):\n",
    "        if(label_count[0]>label_count[1]):\n",
    "            node.label = 1\n",
    "        else:\n",
    "            node.label = 0\n",
    "        return node\n",
    "    \n",
    "    #划分选择，挑选最优的属性作为当前结点\n",
    "    best_attr = AttrSelectBaseGainIndex(data_set,attr_set)\n",
    "    print(data_set)\n",
    "    print(attr_set)\n",
    "    node.attribute = best_attr[0]\n",
    "    for i in best_attr[1]:\n",
    "        node.node_next[i]=Node(None, Node, {})\n",
    "        data_set_a = data_set[data_set[best_attr[0]]==i]#挑选数据集中best_attr[0]属性中包含i值的样例\n",
    "        \n",
    "        if(data_set_a.empty):\n",
    "            if(label_count[0]>label_count[1]):\n",
    "                node.node_next[i].label = 1\n",
    "            else:\n",
    "                node.node_next[i].label = 0\n",
    "            return node\n",
    "        else:\n",
    "            attr_set.pop(best_attr[0],best_attr[1])\n",
    "            node.node_next[i] = CreateDecisionTree(data_set_a, attr_set)  \n",
    "    return node\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "基尼值： 0.5\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 0.31999999999999984\n",
      "基尼指数 0.42745098039215684\n",
      "基尼值： 0.345679012345679\n",
      "基尼值： 0.31999999999999984\n",
      "基尼值： 0.0\n",
      "基尼指数 0.2771241830065359\n",
      "基尼值： 0.40816326530612246\n",
      "基尼值： 0.5\n",
      "基尼值： 0.0\n",
      "基尼指数 0.3445378151260504\n",
      "基尼值： 0.5\n",
      "基尼值： 0.48\n",
      "基尼指数 0.49411764705882355\n",
      "基尼值： 0.46875\n",
      "基尼值： 0.48979591836734704\n",
      "基尼值： 0.0\n",
      "基尼指数 0.42226890756302526\n",
      "基尼值： 0.48\n",
      "基尼值： 0.48\n",
      "基尼值： 0.0\n",
      "基尼指数 0.4235294117647059\n",
      "0.2771241830065359\n",
      "    Color       Root   Knocks   Texture Umbilicus Touch  Label\n",
      "0   green       curl  heavily  distinct    sunken  hard      1\n",
      "1   black       curl     dull  distinct    sunken  hard      1\n",
      "2   black       curl  heavily  distinct    sunken  hard      1\n",
      "3   green       curl     dull  distinct    sunken  hard      1\n",
      "4   white       curl  heavily  distinct    sunken  hard      1\n",
      "5   green  lightCurl  heavily  distinct    dimple  soft      1\n",
      "6   black  lightCurl  heavily      blur    dimple  soft      1\n",
      "7   black  lightCurl  heavily  distinct    dimple  hard      1\n",
      "8   black  lightCurl     dull      blur    dimple  hard      0\n",
      "9   green      stiff    clear  distinct    smooth  soft      0\n",
      "10  white      stiff    clear     fuzzy    smooth  hard      0\n",
      "11  white       curl  heavily     fuzzy    smooth  soft      0\n",
      "12  green  lightCurl  heavily      blur    sunken  hard      0\n",
      "13  white  lightCurl     dull      blur    sunken  hard      0\n",
      "14  black  lightCurl  heavily  distinct    dimple  soft      0\n",
      "15  white       curl  heavily     fuzzy    smooth  hard      0\n",
      "16  green       curl     dull      blur    dimple  hard      0\n",
      "{'Color': array(['green', 'black', 'white'], dtype=object), 'Texture': array(['distinct', 'blur', 'fuzzy'], dtype=object), 'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object), 'Touch': array(['hard', 'soft'], dtype=object), 'Root': array(['curl', 'lightCurl', 'stiff'], dtype=object), 'Knocks': array(['heavily', 'dull', 'clear'], dtype=object)}\n",
      "基尼值： 0.375\n",
      "基尼值： 0.375\n",
      "基尼值： 0.0\n",
      "基尼指数 0.3333333333333333\n",
      "基尼值： 0.0\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 0.0\n",
      "基尼指数 0.14814814814814814\n",
      "基尼值： 0.0\n",
      "基尼值： 0.4444444444444444\n",
      "基尼指数 0.14814814814814814\n",
      "基尼值： 0.0\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 0.0\n",
      "基尼指数 0.14814814814814814\n",
      "基尼值： 0.2777777777777777\n",
      "基尼值： 0.0\n",
      "基尼值： 0.0\n",
      "基尼指数 0.18518518518518512\n",
      "0.14814814814814814\n",
      "    Color       Root   Knocks   Texture Umbilicus Touch  Label\n",
      "0   green       curl  heavily  distinct    sunken  hard      1\n",
      "1   black       curl     dull  distinct    sunken  hard      1\n",
      "2   black       curl  heavily  distinct    sunken  hard      1\n",
      "3   green       curl     dull  distinct    sunken  hard      1\n",
      "4   white       curl  heavily  distinct    sunken  hard      1\n",
      "5   green  lightCurl  heavily  distinct    dimple  soft      1\n",
      "7   black  lightCurl  heavily  distinct    dimple  hard      1\n",
      "9   green      stiff    clear  distinct    smooth  soft      0\n",
      "14  black  lightCurl  heavily  distinct    dimple  soft      0\n",
      "{'Color': array(['green', 'black', 'white'], dtype=object), 'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object), 'Touch': array(['hard', 'soft'], dtype=object), 'Root': array(['curl', 'lightCurl', 'stiff'], dtype=object), 'Knocks': array(['heavily', 'dull', 'clear'], dtype=object)}\n",
      "基尼值： 0.0\n",
      "基尼值： 0.5\n",
      "基尼值： 1.0\n",
      "基尼指数 0.3333333333333333\n",
      "基尼值： 1.0\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 1.0\n",
      "基尼指数 0.4444444444444444\n",
      "基尼值： 0.0\n",
      "基尼值： 0.5\n",
      "基尼指数 0.3333333333333333\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 1.0\n",
      "基尼值： 1.0\n",
      "基尼指数 0.4444444444444444\n",
      "0.3333333333333333\n",
      "    Color       Root   Knocks   Texture Umbilicus Touch  Label\n",
      "5   green  lightCurl  heavily  distinct    dimple  soft      1\n",
      "7   black  lightCurl  heavily  distinct    dimple  hard      1\n",
      "14  black  lightCurl  heavily  distinct    dimple  soft      0\n",
      "{'Color': array(['green', 'black', 'white'], dtype=object), 'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object), 'Touch': array(['hard', 'soft'], dtype=object), 'Knocks': array(['heavily', 'dull', 'clear'], dtype=object)}\n",
      "基尼值： 0.0\n",
      "基尼值： 0.0\n",
      "基尼值： 1.0\n",
      "基尼指数 0.0\n",
      "基尼值： 1.0\n",
      "基尼值： 0.5\n",
      "基尼值： 1.0\n",
      "基尼指数 0.5\n",
      "基尼值： 0.5\n",
      "基尼值： 1.0\n",
      "基尼值： 1.0\n",
      "基尼指数 0.5\n",
      "0.0\n",
      "    Color       Root   Knocks   Texture Umbilicus Touch  Label\n",
      "5   green  lightCurl  heavily  distinct    dimple  soft      1\n",
      "14  black  lightCurl  heavily  distinct    dimple  soft      0\n",
      "{'Color': array(['green', 'black', 'white'], dtype=object), 'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object), 'Knocks': array(['heavily', 'dull', 'clear'], dtype=object)}\n",
      "基尼值： 0.0\n",
      "基尼值： 0.4444444444444444\n",
      "基尼值： 1.0\n",
      "基尼指数 0.26666666666666666\n",
      "基尼值： 0.5\n",
      "基尼值： 0.0\n",
      "基尼值： 1.0\n",
      "基尼指数 0.2\n",
      "0.2\n",
      "    Color       Root   Knocks Texture Umbilicus Touch  Label\n",
      "6   black  lightCurl  heavily    blur    dimple  soft      1\n",
      "8   black  lightCurl     dull    blur    dimple  hard      0\n",
      "12  green  lightCurl  heavily    blur    sunken  hard      0\n",
      "13  white  lightCurl     dull    blur    sunken  hard      0\n",
      "16  green       curl     dull    blur    dimple  hard      0\n",
      "{'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object), 'Knocks': array(['heavily', 'dull', 'clear'], dtype=object)}\n",
      "基尼值： 0.0\n",
      "基尼值： 0.0\n",
      "基尼值： 1.0\n",
      "基尼指数 0.0\n",
      "0.0\n",
      "    Color       Root   Knocks Texture Umbilicus Touch  Label\n",
      "6   black  lightCurl  heavily    blur    dimple  soft      1\n",
      "12  green  lightCurl  heavily    blur    sunken  hard      0\n",
      "{'Umbilicus': array(['sunken', 'dimple', 'smooth'], dtype=object)}\n"
     ]
    }
   ],
   "source": [
    "attr_set = CreateAttrSet(df)\n",
    "root = CreateDecisionTree(df, attr_set)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "DrawPNG(root, \"decision_tree_ID3.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def PredictTree(predict_data,node):\n",
    "    if(node.label!=None):\n",
    "        return node.label\n",
    "    attr_val = predict_data[node.attribute]    \n",
    "    return PredictTree(predict_data,node.node_next[attr_val])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = PredictTree(df.iloc[16],root)\n",
    "print(a)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
